package com.panopset.io;

import static com.panopset.Util.log;
import static com.panopset.Util.x;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import com.panopset.RezIO;

/**
 * Process one or more files.
 *
 * @author Karl Dinwiddie
 */
public final class FileProcessor {

    /**
     * Files.
     */
    private List<File> filez;

    /**
     * Listeners.
     */
    private List<Listener> listenerz;

    /**
     * Done flag.
     */
    private boolean done = false;

    /**
     * Stop processing as soon as possible.
     */
    public void stop() {
        done = true;
    }

    /**
     * Process file(s).
     */
    public void exec() {
        if (filez.isEmpty()) {
            return;
        }
        if (listenerz.isEmpty()) {
            return;
        }
        final String[] lines = new String[filez.size()];
        final FileReader[] fr = new FileReader[filez.size()];
        final BufferedReader[] br = new BufferedReader[filez.size()];
        boolean[] rdrDone = new boolean[filez.size()];

        for (int i = 0; i < filez.size(); i++) {
            rdrDone[i] = false;
            try {
                if (!filez.get(i).exists()) {
                    return;
                }
                if (!filez.get(i).canRead()) {
                    return;
                }
                fr[i] = new FileReader(filez.get(i));
            } catch (FileNotFoundException e) {
                log(e);
                log(x("File not found") + ": "
                        + RezIO.getCanonicalPath(filez.get(i)));
                return;
            }
            br[i] = new BufferedReader(fr[i]);
        }

        while (!done) {
            boolean allDone = true;
            for (int i = 0; i < filez.size(); i++) {
                if (!rdrDone[i]) {
                    try {
                        lines[i] = br[i].readLine();
                        if (lines[i] == null) {
                            rdrDone[i] = true;
                        } else {
                            allDone = false;
                        }
                    } catch (IOException e) {
                        log(e);
                    }
                }
            }
            if (allDone) {
                done = true;
                for (Listener listener : listenerz) {
                    listener.finish(this);
                }

                for (int i = 0; i < filez.size(); i++) {
                    try {
                        br[i].close();
                        fr[i].close();
                    } catch (IOException e) {
                        log(e);
                    }
                }
            } else {
                for (Listener listener : listenerz) {
                    listener.process(this, lines);
                }
            }
        }
    }

    /**
     * Create a single file FileProcessor.
     *
     * @param file
     *            File to process.
     * @param listener
     *            Listener used to process the file.
     */
    public FileProcessor(final File file, final Listener listener) {
        filez = new ArrayList<File>();
        listenerz = new ArrayList<Listener>();
        filez.add(file);
        listenerz.add(listener);
    }

    /**
     * @param files
     *            Files to process.
     * @param listeners
     *            Corresponding listeners.
     */
    private FileProcessor(final List<File> files,
            final List<Listener> listeners) {
        filez = files;
        listenerz = listeners;
    }

    /**
     * File Processor Listener.
     */
    public interface Listener {

        /**
         * Process a single line for each file.
         *
         * @param processor
         *            Processor, in case it is needed.
         * @param s
         *            If more than one file is being processed, and element of s
         *            could be null.
         */
        void process(FileProcessor processor, String[] s);

        /**
         * This method is called after all the files have been processed.
         *
         * @param processor
         *            Processor, in case it is needed.
         */
        void finish(FileProcessor processor);

    }

    /**
     * FileProcessor builder.
     */
    public static final class Builder {

        /**
         * Files to process.
         */
        private final List<File> files = new ArrayList<File>();

        /**
         * Listeners to process files with.
         */
        private final List<Listener> listeners = new ArrayList<Listener>();

        /**
         * Add a File to the processor.
         *
         * @param file File.
         * @return Builder
         */
        public Builder file(final File file) {
            files.add(file);
            return this;
        }

        /**
         * Add a Listener to the processor.
         * @param listener Listener.
         * @return Builder.
         */
        public Builder listener(final Listener listener) {
            listeners.add(listener);
            return this;
        }

        /**
         * Add a File and Listener pair to the processor.
         *
         * @param file
         *            File to add.
         * @param listener
         *            Listener to add.
         * @return Builder.
         */
        public Builder fileAndListener(final File file,
                final Listener listener) {
            files.add(file);
            listeners.add(listener);
            return this;
        }

        /**
         *
         * @return FileProcessor
         */
        public FileProcessor construct() {
            return new FileProcessor(files, listeners);
        }
    }
}
